﻿using System.Text.RegularExpressions;

namespace HttpWrapper;

public class MapHttpCallMiddleware
{
    static private readonly string[] _excludHeaders = new[] { "Content-Length", "Host", "Connection", };
    private readonly RequestDelegate _next;

    public MapHttpCallMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext ctx, ForwardingHttpCall call, IHttpClientFactory httpClientFactory)
    {
        var endpoint = ctx.GetEndpoint();
        if(endpoint == null)
        {
            await _next(ctx);
            return;
        }

        Uri? url;
        try
        {
            url = new Uri(ctx.Request.Query["url"]);
        }
        catch
        {
            ctx.Response.StatusCode = 400;
            await ctx.Response.WriteAsync("invalid url");
            return;
        }

        // 防止渗透内网
        if(Regex.IsMatch(url.Host, @"(^172)|(^192\.168)|(^10)"))
        {
            ctx.Response.StatusCode = 400;
            await ctx.Response.WriteAsync("invalid url");
            return;
        }

        call.Client = httpClientFactory.CreateClient(url.Host);

        var request = new HttpRequestMessage
        {
            RequestUri = url,
            Method = new(ctx.Request.Method),
            Content = new StreamContent(ctx.Request.Body),
        };
        foreach(var header in ctx.Request.Headers.Where(p => !_excludHeaders.Contains(p.Key)))
        {
            request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value as IEnumerable<string?>);
            request.Headers.TryAddWithoutValidation(header.Key, header.Value as IEnumerable<string?>);
        }
        call.Request = request;

        await _next(ctx);

        if(call.Response is not { } response)
            return;

        ctx.Response.Headers.Add("X-Forwarded", true.ToString());
        ctx.Response.StatusCode = (int)response.StatusCode;
        foreach(var header in response.Headers.Concat(response.Content.Headers))
        {
            ctx.Response.Headers.TryAdd(header.Key, new(header.Value.ToArray()));
        }
        ctx.Response.ContentLength = response.Content.Headers.ContentLength;
        await response.Content.CopyToAsync(ctx.Response.Body);
    }
}